package cn.jtfadmin.base.shiro.config;

import cn.jtfadmin.base.lang.config.ApplicationHolder;
import cn.jtfadmin.base.shiro.events.ShiroFilterChainDefinitionChangeEvent;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.AbstractShiroWebFilterConfiguration;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.cache.CacheManager;
import javax.servlet.Filter;
import java.time.LocalDateTime;
import java.util.*;

/**
 * spring shiro web filter配置,
 *
 * @author jtf
 * @see ShiroFilterFactoryBean
 * @since 0.0.1
 */
@Configuration
public class ShiroWebFilterConfig extends AbstractShiroWebFilterConfiguration implements ApplicationListener<ShiroFilterChainDefinitionChangeEvent> {


    /**
     * 定义的过滤器配置
     */
    @Autowired
    protected ObjectProvider<ShiroCustomerFilter> shiroCustomerFilters ;

    /**
     * 全局
     */
    @Autowired
    protected ObjectProvider<ShiroGlobalFilterConfig> shiroGlobalFilterConfigs;


    @Autowired
    protected CacheManager cacheManager;

    protected String cacheName = "shiro_filter_chain_definition";

    protected String cacheKey = "lastUpdateTime";

    protected LocalDateTime lastUpdateTime = LocalDateTime.now();

    @Bean
    @Override
    protected ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(){
            @Override
            protected AbstractShiroFilter createInstance() throws Exception {
                AbstractShiroFilter instance = super.createInstance();
                PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(){
                    @Override
                    public FilterChainManager getFilterChainManager() {
                        LocalDateTime localDateTime = (LocalDateTime) cacheManager.getCache(cacheName).get(cacheKey);
                        if(localDateTime != null && localDateTime.isAfter(lastUpdateTime)){
                            setFilterChainManager(createNewDefaultFilterChainManager((DefaultFilterChainManager) super.getFilterChainManager()));
                            lastUpdateTime = LocalDateTime.now();
                        }
                        return super.getFilterChainManager();
                    }
                };
                chainResolver.setFilterChainManager(
                        ((PathMatchingFilterChainResolver)instance.getFilterChainResolver())
                    .getFilterChainManager());
                instance.setFilterChainResolver(chainResolver);
                return instance;
            }
        };

        shiroFilterFactoryBean.setLoginUrl(loginUrl);
        shiroFilterFactoryBean.setSuccessUrl(successUrl);
        shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setGlobalFilters(globalFilters());
        shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
        shiroFilterFactoryBean.setFilters(filterMap);

        Map<String, Filter> filters =
                shiroFilterFactoryBean.getFilters();
        for (ShiroCustomerFilter shiroCustomerFilter : shiroCustomerFilters) {
            filters.put(shiroCustomerFilter.getName(), shiroCustomerFilter.getFilter());
        }
        return shiroFilterFactoryBean;
    }

    @Bean(name = "globalFilters")
    protected List<String> globalFilters() {
        Set<String> set = new HashSet<>();
        set.addAll(super.globalFilters());
        for (ShiroGlobalFilterConfig shiroGlobalFilterConfig : shiroGlobalFilterConfigs) {
            set.addAll(shiroGlobalFilterConfig.getGlobalFilterNames());
        }
        return new ArrayList<>(set);
    }

    private DefaultFilterChainManager createNewDefaultFilterChainManager(DefaultFilterChainManager oldDefaultFilterChainManager){
        DefaultFilterChainManager newDefaultFilterChainManager = new DefaultFilterChainManager();
        newDefaultFilterChainManager.setFilters(oldDefaultFilterChainManager.getFilters());
        newDefaultFilterChainManager.setFilterConfig(oldDefaultFilterChainManager.getFilterConfig());
        newDefaultFilterChainManager.setGlobalFilters(globalFilters());
        Map<String, String> filterChainMap = ApplicationHolder.getApplicationContext().getBean(ShiroFilterChainDefinition.class).getFilterChainMap();
        if (!CollectionUtils.isEmpty(filterChainMap)) {
            for (Map.Entry<String, String> entry : filterChainMap.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                newDefaultFilterChainManager.createChain(url, chainDefinition);
            }
        }

        newDefaultFilterChainManager.createDefaultChain("/**");
        return newDefaultFilterChainManager;
    }

    @Override
    public void onApplicationEvent(ShiroFilterChainDefinitionChangeEvent event) {
       cacheManager.getCache(cacheName).put(cacheKey, LocalDateTime.now());
    }
}
